Skip to content

feat(workspace): route warehouse tools through the bound workspace's engine - #1168

Draft
ralphstodomingo wants to merge 2 commits into
feat/workspace-engine-overlayfrom
feat/workspace-precedence-v2
Draft

feat(workspace): route warehouse tools through the bound workspace's engine#1168
ralphstodomingo wants to merge 2 commits into
feat/workspace-engine-overlayfrom
feat/workspace-precedence-v2

Conversation

@ralphstodomingo

@ralphstodomingo ralphstodomingo commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1155

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Stacked on #1167 — review that first; this PR is the commit on top. It is the precedence change from #1156 restacked onto the overlay attach; the mechanism is unchanged, the attach seam it reads is now the overlay's.

When a bound workspace's engine is attached, the model gets two ways to do the same thing: the native warehouse tools over local keychain connections, and the engine's MCP tools over the workspace's SaaS connection. Nothing chose between them, so the model picked whichever description read better — and that pick decided which credentials ran the query and whether it was audited (engine calls are audited server-side; native ones are not).

This adds a per-session decision: shadow only what materialised and is attributable to the bound workspace; anything undetermined runs locally and says why; nothing is silent.

  • Materialised, not declared — it reads the engine tool keys actually present, so a declared-but-absent integration shadows nothing.
  • Attributed — routing engages only when the attach outcome is attached (the overlay's own pinned engine, connected at this turn boundary) and the configured entry's pin names the bound workspace. Any other outcome, or none, fails open with a reason.
  • Per capability, not per warehouse type — Snowflake has execute, explain and table stats; BigQuery and Postgres have execute only. Keying on the type would send an explain on BigQuery to a tool that does not exist, and there is deliberately no fallback.
  • Calls naming no warehouse are judged against the target they would really reach, mirroring each handler's own resolution, including the registry connection dbt falls back to.
  • The redirect names the exact engine tool, executes nothing, is marked in metadata so telemetry can tell it from an execution, and is only offered to a caller whose agent may call that tool.
  • It runs after every native safety check — the hard deny on destructive statements and the write confirmation both guard things the engine side has no equivalent for.
  • --integrations=local turns it off for a session.

Two deliberate deviations: the guard needs a companion call to attach the fail-open notice, which a pre-execution check cannot do; and an adjacent warehouse-type reporting bug is left alone, since fixing it changes a shipped telemetry field.

How did you verify your code works?

bun run typecheck clean; precedence, default-target, guard-order and workspace suites pass (161 tests across the six directly affected files; the tool/native/prompt suites green). The union test now asserts the allowlist is exactly attached over the whole outcome union, so a future outcome kind refuses routing by default.

End-to-end rows from #1156 (shadow marking, redirect with a proven no-local-execution control, DuckDB control, default target, model following the redirect unprompted, write confirmation, escape hatch) are re-run on this stack and recorded in the review-log comment below before this leaves draft.

GitGuardian flags a masked placeholder (eight literal asterisks) in a help-text snapshot that only moved columns; it is present unchanged on the base commit and is not a credential.

Screenshots / recordings

n/a — CLI change, no UI.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Summary by cubic

Previously, attaching a bound workspace's engine left the model with two equally-visible ways to run each warehouse operation — native tools over the local keychain connection and the engine's MCP tools over the SaaS connection — and nothing chose between them, so the better-written description decided which credentials ran the query and whether it was audited server-side. A per-session precedence decision now routes each operation to the engine's tool only when that tool is materialised and attach proves the engine owns the bound workspace; anything undetermined runs locally with a stated reason, and --integrations=local disables routing for a session. Closes #1155.

Routing rules

  • Shadowing requires both that the engine tool is materialised (a declared-but-absent integration shadows nothing) and that the engine is attributed: attach outcome attached plus a configured pin naming the bound workspace; any other outcome fails open with a reason.
  • Matching is per capability, not per warehouse type: Snowflake gets execute, explain, and table stats; BigQuery and Postgres get execute only.
  • Calls naming no warehouse are judged against the target they would really reach, mirroring each handler's own resolution, including dbt's registry fallback.
  • The redirect names the exact engine tool, executes nothing, is marked in metadata for telemetry, and appears only to callers whose agent may call that tool.
  • The guard runs after the native safety checks, so the hard deny on destructive statements and the write confirmation are never bypassed.
  • An adjacent warehouse-type reporting bug is left alone because fixing it would change a shipped telemetry field.

Written for commit b8daef2. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitguardian

gitguardian Bot commented Aug 27, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
34260894 Triggered Generic CLI Secret 83c5075 packages/opencode/test/cli/help/snapshots/help-snapshots.test.ts.snap View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@ralphstodomingo

ralphstodomingo commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Review log

Stacked on #1167 — its review-log comment carries the attach contract (claims, disclosed residuals, review policy). This PR is reviewed the same way: a finding is a reproducible trace that violates a claim; rounds are capped.

Claims (in addition to #1167's)

  1. Routing engages only when settledOutcome is attached and the configured datamate entry's pin names the bound workspace; any other outcome, or none, fails open with a stated reason.
  2. Redirects are offered per materialised capability, never per warehouse type; a capability the engine did not materialise runs locally.
  3. A redirect runs after the hard deny on destructive statements and after the write confirmation; it never executes anything itself; it is only offered to a caller whose agent may call the destination tool.
  4. --integrations=local turns routing off for the session.

Disclosed residuals (in addition to #1167's)

  • Routing is derived per turn; within a turn it does not re-derive.
  • Beyond 256 tracked sessions the oldest is dropped and re-derived on its next call.
  • A redirect can name a client another session removed; that is a tool-layer failure, not a query sent to the wrong warehouse.

End-to-end rows (from #1156, re-run on this stack 2026-08-28 against the demo workspace with a Snowflake connection; engine 0.7.0)

# Scenario Result
1 shadow marking per capability; local DuckDB rows unmarked warehouse_list: the Snowflake connection reads "execute/explain/inspect via workspace …", the DuckDB rows "local"
2 redirect, nothing executed — positive control in query history sql_execute on the served connection returned the redirect (redirected: true, redirect_to: datamate_snowflake_execute_database_query, precedence: shadowed). Two markers: a run that followed the redirect through the engine tool shows N=1 in information_schema.query_history (probe excluded); a run that stopped at the redirect shows no row for its marker
3 DuckDB control runs locally ✓ no redirect, executed locally (the "(0 rows)" rendering for a constant select is pre-existing: identical on the attach-only build)
4 asymmetry: an execute-only served type keeps explain local ✓ with a postgresql integration added to the workspace (engine tools: execute + list only) and a local Postgres connection: sql_explain on it ran locally (real EXPLAIN plan, no redirect), sql_execute on it redirected to datamate_postgresql_execute_database_query (precedence: shadowed); warehouse_list marked it "execute via workspace …; explain/inspect local" per capability. Scaffolding (server connection, workspace integration, container) removed afterwards
5 dbt default path blocked by a pre-existing fault on main, unit-covered: ensureDbtAdapter can never initialise under Bun — python-bridge@1.1.0 runs bluebird.promisifyAll(child_process) at module load and Bun throws TypeError: Cannot access invalid private field (evaluating 'this.#stdin') (oven-sh/bun#18693 class; reproduced on Bun 1.3.9/1.3.10/1.3.14/1.4.0, loads fine under Node 22). Verified on an untouched main checkout with a real dbt project (jaffle-shop-core), so the dbt-first sql_execute path has not engaged since it landed (#221); packages/dbt-tools/src/index.ts::diagnose() already names this incompatibility. Not introduced by this stack; the compiled binary embeds the same runtime. decideForTarget / resolveDefaultTarget remain covered by default-target.test.ts.
6 no warehouse named → default target resolved and redirected ✓ with the Snowflake connection first in the connection file: redirect naming the engine tool, "Not run locally"; file restored byte-identical afterwards
7 model follows the redirect unprompted ✓ "Query Snowflake …" in plain words → datamate_snowflake_list_database_connections then datamate_snowflake_execute_database_query, no native call
8 INSERT on the served connection stops at the write gate ✓ "rejected permission to use this specific tool call" (headless auto-rejects the ask); no redirect
9 --integrations=local → plain listing, local execution ✓ listing without the served-by note; sql_execute on the Snowflake connection executed locally, not redirected

Rounds

(none yet)

Codex rounds

round head findings outcome
1 f68a41a8c 0 — "Didn't find any major issues" no change
f68a41a8c893ff8f93 no review round: the three describeNativeTool/describeEngineTool hooks in prompt.ts/tools.ts used the single-line // altimate_change — marker form, which the strict marker guard that runs on pushes to main does not recognise (bun run script/upstream/analyze.ts --markers --base origin/main --strict flagged them; PR runs use the PR base and are non-strict) wrapped in start/end blocks; tools.ts re-formatted (it is prettier-clean on main). No behaviour change; strict guard now passes for the whole stack against main.

CI note — GitGuardian is red on this PR and that is a false positive. The "1 secret" is the literal placeholder github_pat_******** in the --token help text captured by packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap (a snapshot this PR regenerates). It was red on f68a41a8c before the marker-only commit too. Nothing to remediate; every other check is green.

@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review against the numbered claims and the disclosed residuals in the review-log comment on this PR: report only a reproducible trace that violates a numbered claim; an instance of a listed residual is disclosed behaviour, not a finding.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: f68a41a8c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ralphstodomingo added 2 commits August 28, 2026 08:47
…engine

Shadow a native warehouse capability only when the bound workspace's engine
materialised the matching tool and attach attests the engine is its own
(outcome `attached` plus the configured pin); redirect to the exact engine
tool after the native safety checks; fail open with a reason otherwise.
`--integrations=local` turns it off. Restacked onto the derived-overlay
attach; the allowlist is exactly `attached`.
…ormat tools.ts

The two `describeNativeTool` call sites used the single-line marker form, which
the strict marker guard that runs on pushes to main does not recognise. No
behaviour change.
@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-precedence-v2 branch from 893ff8f to b8daef2 Compare August 28, 2026 00:48

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consensus review from an 8-model panel (Claude + GPT 5.4 Codex, Gemini 3.1 Pro, Kimi K2.5, MiniMax M2.7, GLM-5.1, Qwen 3.6), two convergence rounds. Reviewed against the numbered claims and disclosed residuals in the review-log comment; instances of a listed residual are not reported.

No blockers. 6 major, 6 minor, 3 nit. The major items are inline below; minor and nit items are in a separate comment.

Fix first: MAJOR #1 — when the engine can't be attributed, the call runs locally with no notice and no undetermined marker in the result. The only channel is a TUI toast, so in headless there is no signal at all. It fires on every affected session rather than under a race, and it means the pilot's own telemetry can't distinguish "routed" from "quietly didn't". A few lines in check().

The remaining majors read as reasonable pilot residuals. Note that MAJOR #2's inherited half (a mutable registry each handler re-resolves independently) and MINOR M0's inherited half (the attached outcome carrying no workspace identity, in engine-types.ts) both live below this PR in the stack.

The design itself held up well under seven independent reads — capability-scoped shadowing, the reachable() gating, canonicalType inverting DRIVER_MAP, and the announcement machinery were all singled out as correct, and the guard-ordering test genuinely proves its invariant rather than asserting it. Every finding here is about a seam, not the shape of the decision.

try {
const directory = Instance.directory
if (!directory) return null
const binding = await readLocalBinding(directory)

@sahrizvi sahrizvi Aug 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR — workspace identity drops the credential scope, so a redirect can cross tenants

Severity note: raised by the panel as a blocker on the strength of "cross-tenant". Recording it as MINOR — each customer occupies a single tenant, so no customer can reach this. The only actor is an internal staff session switching tenants mid-flight, inside a roughly one-turn window. Real defect, narrow and internally-bounded trigger, cheap fix.

readLocalBinding is readLocalBindingScoped(...).binding — it discards the scope. The subsystem this sits on top of deliberately does not, and says why:

// engine-overlay.ts:151-156
/** Identity of the workspace a binding names: the credential scope it was
 * read under plus the tenant-local id. */
function workspaceKey(binding: ScopedBinding): string {
  return `${binding.scope ?? ""}|${binding.datamateId}`
}

state.ts:195-205 exists purely to carry that scope (tenant|apiUrl): "Workspace ids are tenant-local; the scope is what tells the same id in two tenants apart."

Trace: a session attaches under tenant A / workspace 42. The user switches credential scope and links tenant B / workspace 42 before the next engine boundary. Then:

  • attested() passes — the settled outcome is { kind: "attached"; available; declared?; missing? } (engine-types.ts:26) and carries no workspace identity at all.
  • attributedTo() passes — the configured pin is --datamate 42, scope-free by construction.
  • the re-link guard at precedence.ts:565 passes — it compares 42 !== Number("42").

Precedence engages and issues a redirect naming the engine tool that the pinned MCP wrapper still points at, which is tenant A's engine — so the query runs on the wrong credentials and is audited against the wrong workspace. Low-numbered id collisions across tenants (a demo workspace 1, a customer workspace 1) are ordinary.

It is silent when it happens: it would surface as a query in a customer's audit log that nobody on their side ran, not as anything visible in telemetry.

Fix — thread the scope through, as the overlay already does:

  • read via readLocalBindingScoped here;
  • store workspaceKey = \${scope}|${datamateId}`inPrecedenceinstead ofworkspaceId (precedence.ts:419, :565`);
  • carry the applied workspace key on the settled attached outcome, and require settled identity, current binding and snapshot to match exactly;
  • when the scope is unavailable, run locally with an undetermined notice.

Note on ownership: the fix splits across the stack. Using the scoped reader is this PR. Putting identity on the attached outcome is engine-types.ts, i.e. #1167 — untouched here. Worth deciding which PR carries which half, or it falls between the two.

precedence: "undetermined",
}
}
if (!precedence.enabled) return RUN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR — unattributed runs locally with no notice and no marker

if (!precedence.enabled) return RUN fires for every disabled reason, unattributed included. RUN is the empty verdict: no notice, no precedence: "undetermined" metadata. The only place the reason surfaces is a fire-and-forget TuiEvent.ToastShow.

In headless run/serve, when the event bridge fails, or simply before the toast lands, a session whose engine could not be attributed executes locally, skips the server-side audit, and says nothing in the result. That contradicts the module's own opening principle ("anything undetermined runs locally with an explicit notice; nothing is ever silent") and Claim 1 ("any other outcome, or none, fails open with a stated reason"). The toast is UI, not the correctness mechanism.

The same asymmetry appears just below at :575-576: an explicitly named connection whose configured type will not canonicalise returns a bare RUN, while the default-target path reports exactly that condition with a notice.

Fix: return a notice with precedence: "undetermined" for unattributed, and for an unrecognisable named type. pilot-off, escape-hatch, unbound and nothing-materialised can stay bare RUN — those are deliberate disablement, not uncertainty.

// computed against (a concurrent `warehouse.add` can change which name sorts first).
// Reading once here makes the decided connection and the executed connection the
// same by construction. The dbt-first ordering below is unchanged.
const fallbackName = params.warehouse || Registry.list().warehouses[0]?.name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR — time-of-check/time-of-use between the routing decision and the executed target

This pin, and the check at :497-501, close the window across the dbt await. But the routing decision was made earlier and elsewhere: Precedence.check()resolveDefaultTarget (register.ts:139-160) does its own Registry.list().warehouses[0] read from inside the tool body, and the handler then resolves the target again, independently. The await Dispatcher.call(...) boundary and the handler's own awaits are enough for a queued concurrent mutation to land in between, so the comment's claim that this makes the decided and executed connection "the same by construction" is stronger than what the pin actually does.

Concretely:

  • the guard sees an unserved DuckDB default; a concurrent warehouse.remove drops it; sql.explain or schema.inspect then picks the newly-first Snowflake connection and executes it locally, despite Snowflake being shadowed — unaudited execution on a served connection, the exact outcome this design exists to prevent;
  • for an explicit name, a concurrent warehouse.add can replace that name with a served type after check() read it. The handler pins the already-replaced type and sees no subsequent change, so this check cannot detect that window.

Note also that this pin exists only in register("sql.execute")sql.explain (:552-570) and schema.inspect (:678-691) have no equivalent guard at all.

Fix: make the decision and the target acquisition atomic — move the precedence check into the handler after it pins the target (passing sessionID through), or return a lease {name, canonicalType, generation} that handlers must revalidate. Apply it to all three ops, explicit names included.

Related, same seam: Precedence.check()'s await import("../native/connections/register") (precedence.ts:586-588) has no try/catch, and check() is called outside the surrounding try in all three tool bodies — so a throw there takes out sql_execute, sql_explain and schema_inspect together instead of failing open.

default-target.test.ts:123-151 does not prove its stated invariant: it calls the dispatcher directly, omitting the preceding precedence decision, which is where the race actually is.

// when the cached answer is about to enable, and leave the refusing path cheap
// rather than re-reading all config on every turn.
if (cached !== expected) return cached
await Config.invalidate().catch((err) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR — Config.invalidate() flushes the global cache and every instance's cache, once per turn; and a failed invalidation is trusted

Two things in these twelve lines.

(a) Blast radius. Config.invalidate() runs invalidateGlobal and invalidateAllInstances() (config/config.ts:827-831, ScopedCache.invalidateAll) — the call-site comment reasons about a per-instance cache, but this is process-wide. It runs whenever the cached pin already matches, which for an engaged session is every turn (refresh is called per turn from prompt.ts:1763-1774). In a long-running multi-directory serve, one active session's precedence refresh invalidates configuration for every other project, repeatedly, and active directories can end up thrashing each other's caches.

(b) A failed invalidation is swallowed. The .catch() logs and continues, so the second read() returns the same cached value, it matches expected, and routing engages on a pin that may no longer be on disk. If an IDE rewrote the entry from workspace 42 to 99 between turns and the invalidation fails, this returns the stale "42". Everywhere else this module refuses when it cannot establish attribution; this is the one path that proceeds instead — and it is the direction the function's own comment calls dangerous.

Fix: (a) don't invalidate globally on this hot path — expose a current-instance-only invalidation, do a narrow uncached read of the datamate entry, or use the overlay's attested applied identity (which would also address the scope blocker). (b) return null when the invalidation throws.

// is what keeps precedence correct when an engine's tool set changes under us.
// Resolved before the loops below because both sides' descriptions depend on it.
const mcpTools = await MCP.tools()
const precedence = await Precedence.refresh(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR — precedence ignores per-turn tool availability

refresh() is derived from the full materialised MCP map plus permission rules. But resolveTools in llm.ts:309-316 deletes any tool where input.user.tools?.[tool] === false, after precedence has been computed here.

So a request that disables datamate_snowflake_execute_database_query for the turn still gets sql_execute shadowed: the native description falsely claims redirection, and the redirect names a tool that is not in that turn's catalogue. A working local operation becomes a dead end.

reachable() was written to prevent exactly this class of dead end for permissions — the same reasoning applies to availability.

Fix: derive precedence from the effective catalogue after user.tools toggles are applied, or pass an availableToolKeys set into refresh() and require materialised and available. Keep the permission-rule check as a separate condition.


function bindTo(id = 42, name = "analytics") {
precedenceInternals.binding = async () => ({ datamateId: id, datamateName: name })
precedenceInternals.attributedTo = async () => String(id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR (testing) — mechanism 1a, attribution, is never exercised

Every test in this suite and in precedence-guard-order.test.ts:47-51 assigns precedenceInternals.attributedTo, .binding and .attachOutcome. The real attributedTo — the Config.get() read, the pinnedWorkspace parse, and the "confirm against disk only when the cached answer is about to enable" logic — is never executed by anything.

That is where three of this review's findings live: the credential-scope blocker, the global Config.invalidate() on the hot path, and the swallowed invalidation that trusts a stale pin. None of them could have been caught by CI, because no test reaches the code. The 1000 lines here prove the decision tree is right; they prove nothing about its inputs.

This needs an integration-shaped test, not a unit test: drive attributedTo against a temp config with pinned / unpinned / re-pinned datamate entries, and add one for the same numeric id under two credential scopes.

Minor, same file: resetForTests() (precedence.ts:438-446) deletes announce, binding and attributedTo but not attachOutcome, so that seam leaks between test files.

@sahrizvi

sahrizvi commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Consensus review — minor, nit, and rejected findings

Companion to the inline review (6 major, no blockers). Panel: Claude + GPT 5.4 Codex, Gemini 3.1 Pro, Kimi K2.5, MiniMax M2.7, GLM-5.1, Qwen 3.6 — two convergence rounds. Reviewed against the numbered claims and disclosed residuals; instances of a listed residual are not reported.


MINOR

M0. Workspace identity drops the credential scopeprecedence.ts:265-271, :419, :565 (inline)

Recorded as MINOR rather than the blocker the panel first ranked it. Each customer occupies a single tenant, so no customer can reach it; the only actor is an internal staff session switching tenants mid-flight, in a roughly one-turn window. Real defect, cheap fix, deliberately not scheduled — details and the ownership split with #1167 are in the inline comment.

M1. warehouse_list reports against a snapshot it never re-validateswarehouse-list.ts:28-46

It reads Precedence.forSession() and annotates rows with no binding check, while check() does re-validate (precedence.ts:565). After a mid-turn re-link the query tools correctly refuse the stale redirect and run locally with a notice, but warehouse_list goes on claiming those same connections are served by the old workspace — violating the invariant its own tests assert, that no surface claims a routing that will not happen. Expose the snapshot-validity check and use it before producing notes.

M2. Hardcoded engine-tool conventions fail silentlyprecedence.ts:81-87 (engineToolFor), precedence.ts:91-96 (INTEGRATION_TYPE)

Both are hand-maintained. A new engine integration (redshift), or a renamed engine tool (databricks_execute_sql → anything else), materialises normally and shadows nothing — the local call keeps running unaudited with no notice and no log line. Fail-safe, but silent, which is the one thing the module's opening principle rules out. A warning when a materialised *_execute_database_query-shaped key matches no known integration, plus a test asserting every INTEGRATION_TYPE value is a canonical DRIVER_MAP target, would make the drift visible. (The widest agreement in this review — four models raised it independently.)

M3. The dbt-fallback redirect names the fallback connection and offers no way to insist on dbtprecedence.ts:630-641

With a dbt project on DuckDB (known, unserved) and a served Postgres as the registry fallback, the call is redirected to datamate_postgresql_execute_database_query. Shadowing a served fallback is a deliberate fail-closed choice — the dbt attempt genuinely can yield nothing and land on that connection — so this is not mis-routing, and nothing executes. The problem is the message: it names the fallback connection as the thing the workspace serves, and the two ways out it offers are warehouse=<fallback> or --integrations=local. Neither lets the caller say "I meant the dbt path". Unreachable in production today given E2E row 5, so this matters when the dbt path is revived.

M4. resetDbtAdapter breaks the single-flight contractregister.ts:57-98

The finally clears dbtAdapterInflight unconditionally. If resetDbtAdapter() runs mid-flight it also clears it; the next call creates a new promise, and when the original settles its finally clears the new one. Test-only today, but a source of flakiness. Guard the clear: if (dbtAdapterInflight === mine) dbtAdapterInflight = undefined.

M5. Two coverage gaps CI cannot seedefault-target.test.ts:62-84, :107, :113-120

The dbt-fallback tests are if (target.source === "dbt") {…} else {…} and the suite header notes it runs outside a dbt project, so the else always wins: adapterTypeFromManifest, the getAdapterType?.() / "unknown" coalescing and the construction of { source: "dbt", type, fallback } have no coverage at all. A branch-tolerant assertion cannot fail, so the gap is invisible. Given E2E row 5 the stakes are low — but then the module comments should say the dbt branch is currently dead rather than reading as if it is live.

Separately, "repeated resolution is stable and does not rebuild state" asserts only that three results match. It never counts adapter creations, so it does not test single-flight at all.


NIT

  • The audit boundary covers three tools, not the warehouse surface. pii-detector.ts:135, schema/tags.ts:73,140, data-diff.ts:183-189, local/schema-sync.ts:84 and the FinOps modules reach Registry.get() and run SQL on the local connection with no precedence check. When the engine serves Snowflake, schema_detect_pii still runs unaudited against the same credentials. Not fixable here — the engine serves no equivalent capability — but worth stating as a known limit rather than leaving it implied.
  • publishQueue keeps a settled promise per live session (precedence.ts:227,348-360). Three models called this an unbounded chain; it is one promise per session, replaced on each publish and bounded by MAX_TRACKED_SESSIONS, so there is no leak. Deleting the entry when the chain is still the tail is tidy-up, not a fix.
  • --integrations=local is process-wide, not session-wide. index.ts writes process.env.ALTIMATE_INTEGRATIONS in middleware and it is inherited by child processes. Correct for the CLI; the comments consistently say "for the session", which is not what serve gets.

Additional missing tests

Beyond those named inline:

  1. The same numeric workspace id under two credential scopes, with a mid-turn account switch.
  2. Registry mutation between Precedence.check() and each dispatcher handler — default and explicit targets, all three ops.
  3. An unattested engine in headless mode, asserting the result carries the fail-open reason.
  4. A per-turn tools: { datamate_…: false } toggle.
  5. A mid-turn re-link followed by warehouse_list.
  6. A databricks redirect end-to-end is still worth having — only Snowflake and BigQuery are exercised.

(A previous version of this comment listed a followed schema_inspect redirect here, on the concern that <id>_get_table_stats might not return column metadata. Checked against the engine: snowflake_get_table_stats returns ColumnMetadata[] = {name, type, nullable, default_value, primary_key, unique_key, comment}, which is a superset of what native schema_inspect returns — the native handler hardcodes primary_key: false with a note that real PK detection "would need additional query". So the redirect is an upgrade, not a dead end, and the concern is withdrawn.)


Raised and rejected

Recorded so they are not raised again in a later round:

  • "warehouse_list claims a routing the caller cannot follow." Wrong: warehouseListNote computes through servedFor, which filters on reachable. Covered by precedence.test.ts:463,468,488. (The real warehouse_list issue is M1 — a stale snapshot, not permissions.)
  • "A hard-denied statement is redirected to the engine tool." Wrong as located: the deny throws before the guard runs, proved by precedence-guard-order.test.ts.
  • "--integrations=workspaces silently evaluates to false." Wrong: yargs choices: ["workspace","local"] rejects the value at parse time.
  • "An unguarded throw in Precedence.check() is only MINOR, since a tool crash is not a security boundary failure." Partly fair — the query does not execute — but it takes out sql_execute, sql_explain and schema_inspect together and contradicts the module's "undetermined runs locally and says so" invariant. Kept, folded into the TOCTOU finding on register.ts:476.
  • A finding about the write confirmation not binding the eventual engine call. Withdrawn: the engine tool has been callable since feat(workspace): attach the bound workspace's engine as a derived MCP overlay #1167 and this PR does not touch the MCP execution path, so it is not this change's to answer.
  • Session-id reuse after eviction resurrecting a stale announcement. Non-actionable with ULIDs.
  • A determined-but-unserved dbt type being "routed to the wrong database." Downgraded to M3: the fail-closed choice is intentional and nothing executes; the defect is the message, not the routing.

What holds up

Worth saying, because seven independent reads converged on it: the design is right and the findings are all about seams.

  • Guard ordering is correct and provedprecedence-guard-order.test.ts fails if the guard moves above the hard deny or the write ask. Most changes assert this in a comment; this one tests it.
  • canonicalType inverting DRIVER_MAP instead of restating it, and deliberately keeping redshift distinct from postgres.
  • The SERVING table making a new attach-outcome kind fail compilation until it is classified, with the safe default.
  • The announcement machinery — announced vs publishing, the per-session publish chain, recording only confirmed deliveries. The tests cover the awkward interleavings: failed delivery, correction in flight, stop-before-announce-lands.
  • Capability-scoped rather than type-scoped shadowing, with a test that sql_explain on BigQuery is not redirected to a tool that does not exist.
  • annotate on every exit path including the error paths, where the marker matters most.
  • The dbtAdapterInflight single-flight fix is a real bug fix independent of this feature.
  • Only materialised datamate_* keys confer precedence; a declared-but-absent integration shadows nothing.
  • The capability map is correct against the engine. Checked altimate-mcp-engine directly: Snowflake exposes execute_database_query / get_query_explain_plan / get_table_stats; BigQuery, PostgreSQL and Databricks expose execute only; databricks_execute_sql really does break the <id>_execute_database_query convention. INTEGRATION_TYPE, engineToolFor and the per-capability asymmetry all match what the engine actually serves, and the shared sqlAlchemyToolsBase adds nothing beyond execute + list. M2 is therefore a forward-looking maintainability concern, not a present mismatch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants